package cz.cuni.mff.ufal.volk.utils;

import java.io.IOException;
import java.io.InputStream;

/**
 * A wrapper stream that provides the possibility to read both binary and text data from
 * the underlying stream. In addition to all standard {@code InputStream} methods, it provides
 * the method {@code #nextLine()} to read the ASCII data from the stream. The {@code #read()}
 * methods and the {@code #nextLine()} method can be mixed.
 *
 * <p>The {@code #nextLine()} method calls {@code #read()}, {@code #mark(int)}
 * and {@code #reset()} methods of the underlying stream. Therefore, the underlying stream
 * should support resetting the stream. Moreover, the {@code #read()} method should be
 * efficient. It is advised to use e.g. a {@code BufferedInputStream} as the underlying stream.
 * </p>
 *
 * @author Bartłomiej Etenkowski
 */
public class MixedAsciiInputStream extends InputStream {

  /**
   *
   * @param in the underlying stream
   * @throws NullPointerException if the argument is null
   * @throws IllegalArgumentException if the stream passed by the in argument does not support
   *         mark and reset operations
   */
  public MixedAsciiInputStream(InputStream in) {
    in.getClass(); // to ensure that in is not null
    if (!in.markSupported())
      throw new IllegalArgumentException("The in stream does not support mark and reset operations");
    this.in = in;
  }

  private final InputStream in;

  @Override
  public int read() throws IOException {
    return in.read();
  }

  @Override
  public int read(byte[] b, int off, int len) throws IOException {
    return in.read(b, off, len);
  }

  /**
   * Reads the data from the underlying stream and converts them into ASCII string. The data is
   * being read until the end of line (either \r or \n character or both) is reached.
   *
   * @return the line that has been read (without the end of line characters)
   * @throws IOException
   */
  public String nextLine() throws IOException {
    StringBuilder sb = new StringBuilder();
    char c;
    boolean retPrevious = false;
    while ((c = (char)in.read()) != -1) {
      if (c == '\n')
        break;
      if (c == '\r') {
        if (retPrevious) {
          in.reset();
          break;
        } else {
          retPrevious = true;
          in.mark(2);
          continue;
        }
      }
      sb.append(c);
    }
    return sb.toString();
  }

}
